-- Copyright (c) 2007 Maksym Motornyy <mmotorny@gmail.com>
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.

module HPython.Interpreter where

import Control.Exception
import Control.Monad.State
import Data.Bits
import Data.Complex
import qualified Data.Map as Map
import Data.Typeable
import HPython.AbstractSyntaxTree

-- TODO: check associativity and precedence of all operators
-- TODO: all fromInteger conversions probably should throw exception

data PythonObject
	= PythonBoolean Bool
	| PythonInteger Integer
	| PythonFloatingPoint Double
	| PythonComplex (Complex Double)
	| PythonString String
	| PythonList [PythonObject]
	| PythonTuple [PythonObject]
	-- TODO: objects, functions, etc.

showCommaSeparatedList :: [PythonObject] -> (String -> String) -> String

showCommaSeparatedList [] _ =
	""

showCommaSeparatedList (_firstObject:_restObjects) _mapString =
	", " ++ 
	objectToString _firstObject _mapString ++ 
	showCommaSeparatedList _restObjects _mapString

listToString :: [PythonObject] -> (String -> String) -> String

listToString [] _ =
	""

listToString (_firstObject:_restObjects) _mapString =
	objectToString _firstObject _mapString ++ 
	showCommaSeparatedList _restObjects _mapString

tupleToString :: [PythonObject] -> (String -> String) -> String

tupleToString [] _ =
	""

tupleToString (_firstObject:_restObjects) _mapString =
	objectToString _firstObject _mapString ++ 
	(if null _restObjects 
		then "," 
		else showCommaSeparatedList _restObjects _mapString
	)

objectToString :: PythonObject -> (String -> String) -> String
objectToString _object _mapString =
	case _object of
		PythonBoolean _value -> show _value
		PythonInteger _value -> show _value
		PythonFloatingPoint _value -> show _value
		PythonComplex _value -> show _value
		PythonString _value -> _mapString _value
		PythonList _value -> "[" ++ listToString _value _mapString ++ "]"
		PythonTuple _value -> "(" ++ tupleToString _value _mapString ++ ")"

data PythonException
	= IndexError
	| NameError
	| TypeError
	| ZeroDivisionError
	deriving Typeable
	
instance Show PythonException where
	show _exception =
		case _exception of
			IndexError -> "IndexError"
			NameError -> "NameError"
			TypeError -> "TypeError"
			ZeroDivisionError -> "ZeroDivisionError"

data
	ExecutionFrame =
	ExecutionFrame
	{
		parentExecutionFrame :: Maybe ExecutionFrame,
		nameToObjectMap :: Map.Map String PythonObject
	}

isTrue :: PythonObject -> Bool
isTrue _object =
	case _object of
		PythonBoolean _value -> _value
		PythonInteger _value -> _value /= 0
		PythonFloatingPoint _value -> _value /= 0.0
		PythonComplex _value -> _value /= 0.0 :+ 0.0
		PythonString _value -> not $ null _value
		PythonList _elements -> not $ null _elements
		PythonTuple _elements -> not $ null _elements

boolToInteger :: Bool -> Integer
boolToInteger _value =
	case _value of
		False -> 0
		True -> 1

logicalNot :: PythonObject -> IO PythonObject
logicalNot _object =
	return $ PythonBoolean $ not $ isTrue _object

bitwiseNot :: PythonObject -> IO PythonObject
bitwiseNot _object =
	case _object of
		PythonBoolean _value ->
			return $ PythonInteger $ complement $ boolToInteger _value
		PythonInteger _value -> 
			return $ PythonInteger $ complement _value
		_ -> 
			throwDyn TypeError

unaryPlus :: PythonObject -> IO PythonObject
unaryPlus _object =
	return _object

unaryMinus :: PythonObject -> IO PythonObject
unaryMinus _object =
	case _object of
		PythonBoolean _value ->
			return $ PythonInteger $ -boolToInteger _value
		PythonInteger _value -> 
			return $ PythonInteger $ -_value
		PythonFloatingPoint _value -> 
			return $ PythonFloatingPoint $ -_value
		PythonComplex _value -> 
			return $ PythonComplex $ -_value
		_ -> 
			throwDyn TypeError

symmetricCoerce :: PythonObject -> PythonObject -> (PythonObject, PythonObject)
symmetricCoerce _leftObject _rightObject =
	let
		(_coercedRightObject, _coercedLeftObject) = 
			coerce _rightObject _leftObject
	in
		(_coercedLeftObject, _coercedRightObject)

coerce :: PythonObject -> PythonObject -> (PythonObject, PythonObject)

-- Complex coercions.

coerce _leftObject@(PythonComplex _) (PythonBoolean _value) =
	(_leftObject, PythonComplex $ (fromIntegral $ boolToInteger _value) :+ 0.0)

coerce _leftObject@(PythonComplex _) (PythonInteger _value) =
	(_leftObject, PythonComplex $ fromIntegral _value :+ 0.0)

coerce _leftObject@(PythonComplex _) (PythonFloatingPoint _value) =
	(_leftObject, PythonComplex $ _value :+ 0.0)

coerce _leftObject@(PythonComplex _) _rightObject@(PythonComplex _) =
	(_leftObject, _rightObject)

coerce _leftObject _rightObject@(PythonComplex _) =
	symmetricCoerce _leftObject _rightObject

-- Floating point coercions.

coerce _leftObject@(PythonFloatingPoint _) (PythonBoolean _value) =
	(_leftObject, PythonFloatingPoint $ fromIntegral $ boolToInteger _value)

coerce _leftObject@(PythonFloatingPoint _) (PythonInteger _value) =
	(_leftObject, PythonFloatingPoint $ fromIntegral _value)

coerce _leftObject@(PythonFloatingPoint _) _rightObject@(PythonFloatingPoint _) =
	(_leftObject, _rightObject)

coerce _leftObject _rightObject@(PythonFloatingPoint _) =
	symmetricCoerce _leftObject _rightObject

-- Integer coercions.

coerce _leftObject@(PythonInteger _) (PythonBoolean _value) =
	(_leftObject, PythonInteger $ boolToInteger _value)

coerce _leftObject@(PythonInteger _) _rightObject@(PythonInteger _) =
	(_leftObject, _rightObject)

coerce _leftObject _rightObject@(PythonInteger _) =
	symmetricCoerce _leftObject _rightObject

-- Universal coercion.

coerce _leftObject _rightObject =
	(_leftObject, _rightObject)
	
applyToCoerced
	:: (PythonObject -> PythonObject -> a) 
	-> PythonObject 
	-> PythonObject 
	-> a
applyToCoerced _operator _leftObject _rightObject =
	let
		(_coercedLeftObject, _coercedRightObject) =
			coerce _leftObject _rightObject
	in
		_leftObject `_operator` _rightObject

bitwiseBinaryOperation'
	:: (Bool -> Bool -> PythonObject) 
	-> (Integer -> Integer -> PythonObject)
	-> PythonObject
	-> PythonObject 
	-> IO PythonObject
	
bitwiseBinaryOperation'
	_operator 
	_
	(PythonBoolean _leftValue)
	(PythonBoolean _rightValue)
	=
		return $ _operator _leftValue _rightValue

bitwiseBinaryOperation'
	_
	_operator
	(PythonInteger _leftValue)
	(PythonInteger _rightValue)
	=
		return $ _operator _leftValue _rightValue
	
bitwiseBinaryOperation' _ _ _ _ =
	throwDyn TypeError

bitwiseBinaryOperation
	:: (Bool -> Bool -> Bool) 
	-> (Integer -> Integer -> Integer)
	-> PythonObject
	-> PythonObject 
	-> IO PythonObject
bitwiseBinaryOperation _boolOperator _integerOperator =
	bitwiseBinaryOperation'
		(\_leftValue _rightValue ->
			PythonBoolean $ _leftValue `_boolOperator` _rightValue
		)
		(\_leftValue _rightValue ->
			PythonInteger $ _leftValue `_integerOperator` _rightValue
		)

bitwiseAnd :: PythonObject -> PythonObject -> IO PythonObject
bitwiseAnd =
	bitwiseBinaryOperation (&&) (.&.)

bitwiseOr :: PythonObject -> PythonObject -> IO PythonObject
bitwiseOr =
	bitwiseBinaryOperation (||) (.|.)

bitwiseXor :: PythonObject -> PythonObject -> IO PythonObject
bitwiseXor =
	bitwiseBinaryOperation (/=) xor

shiftOperation
	:: (Integer -> Int -> Integer)
	-> PythonObject
	-> PythonObject 
	-> IO PythonObject
shiftOperation _shiftOperator =
	bitwiseBinaryOperation'
		(\_leftValue _rightValue -> 
			PythonInteger $ 
				_shiftOperator 
					(boolToInteger _leftValue) 
					(fromInteger $ boolToInteger _rightValue)
		)
		(\_leftValue _rightValue ->
			-- FIXME: throw exception if shift is greater or equal to 2^32
			PythonInteger $ _shiftOperator _leftValue (fromInteger _rightValue)
		)

leftShift :: PythonObject -> PythonObject -> IO PythonObject
leftShift =
	shiftOperation shiftL

rightShift :: PythonObject -> PythonObject -> IO PythonObject
rightShift =
	shiftOperation shiftR

addition :: PythonObject -> PythonObject -> IO PythonObject

addition (PythonBoolean _leftValue) (PythonBoolean _rightValue) =
	return $ 
		PythonInteger $ 
			(boolToInteger _leftValue) + (boolToInteger _rightValue)

addition (PythonInteger _leftValue) (PythonInteger _rightValue) =
	return $ PythonInteger $ _leftValue + _rightValue

addition (PythonFloatingPoint _leftValue) (PythonFloatingPoint _rightValue) =
	return $ PythonFloatingPoint $ _leftValue + _rightValue

addition (PythonComplex _leftValue) (PythonComplex _rightValue) =
	return $ PythonComplex $ _leftValue + _rightValue
	
addition (PythonString _leftValue) (PythonString _rightValue) =
	return $ PythonString $ _leftValue ++ _rightValue

addition (PythonList _leftElements) (PythonList _rightElements) =
	return $ PythonList $ _leftElements ++ _rightElements

addition (PythonTuple _leftElements) (PythonTuple _rightElements) =
	return $ PythonTuple $ _leftElements ++ _rightElements

addition _ _ =
	throwDyn TypeError

subtraction :: PythonObject -> PythonObject -> IO PythonObject

subtraction (PythonBoolean _leftValue) (PythonBoolean _rightValue) =
	return $ 
		PythonInteger $ 
			(boolToInteger _leftValue) - (boolToInteger _rightValue)

subtraction (PythonInteger _leftValue) (PythonInteger _rightValue) =
	return $ PythonInteger $ _leftValue - _rightValue

subtraction (PythonFloatingPoint _leftValue) (PythonFloatingPoint _rightValue) =
	return $ PythonFloatingPoint $ _leftValue - _rightValue

subtraction (PythonComplex _leftValue) (PythonComplex _rightValue) =
	return $ PythonComplex $ _leftValue - _rightValue
	
subtraction _ _ =
	throwDyn TypeError

sequenceMultiplication 
	:: ([element] -> PythonObject)
	-> [element]
	-> Integer 
	-> IO PythonObject
sequenceMultiplication _constructor _sequence _factor =
	return $ 
		_constructor $ 
			concat $ replicate (fromInteger _factor) _sequence

multiplication :: PythonObject -> PythonObject -> IO PythonObject

-- Boolean multiplication.

multiplication (PythonBoolean _leftValue) (PythonBoolean _rightValue) =
	return $ 
		PythonInteger $ 
			(boolToInteger _leftValue) * (boolToInteger _rightValue)

-- Integer multiplication.

multiplication (PythonInteger _leftValue) (PythonInteger _rightValue) =
	return $ PythonInteger $ _leftValue * _rightValue

-- Floating point multiplication.

multiplication (PythonFloatingPoint _leftValue) (PythonFloatingPoint _rightValue) =
	return $ PythonFloatingPoint $ _leftValue * _rightValue

-- Complex multiplication.

multiplication (PythonComplex _leftValue) (PythonComplex _rightValue) =
	return $ PythonComplex $ _leftValue * _rightValue

-- String multiplication.

multiplication _leftObject@(PythonString _) (PythonBoolean _rightValue) =
	multiplication _leftObject (PythonInteger $ boolToInteger _rightValue)

multiplication (PythonString _leftValue) (PythonInteger _rightValue) =
	sequenceMultiplication PythonString _leftValue _rightValue

multiplication _leftObject _rightObject@(PythonString _) =
	multiplication _rightObject _leftObject

-- List multiplication.

multiplication _leftObject@(PythonList _) (PythonBoolean _rightValue) =
	multiplication _leftObject (PythonInteger $ boolToInteger _rightValue)

multiplication (PythonList _leftValue) (PythonInteger _rightValue) =
	sequenceMultiplication PythonList _leftValue _rightValue

multiplication _leftObject _rightObject@(PythonList _) =
	multiplication _rightObject _leftObject
	
-- Tuple multiplication.

multiplication _leftObject@(PythonTuple _) (PythonBoolean _rightValue) =
	multiplication _leftObject (PythonInteger $ boolToInteger _rightValue)

multiplication (PythonTuple _leftValue) (PythonInteger _rightValue) =
	sequenceMultiplication PythonTuple _leftValue _rightValue

multiplication _leftObject _rightObject@(PythonTuple _) =
	multiplication _rightObject _leftObject

-- Invalid multiplication.

multiplication _ _ =
	throwDyn TypeError

division :: PythonObject -> PythonObject -> IO PythonObject

division (PythonBoolean _leftValue) (PythonBoolean _rightValue) =
	division 
		(PythonInteger $ boolToInteger _leftValue) 
		(PythonInteger $ boolToInteger _rightValue)

division (PythonInteger _leftValue) (PythonInteger _rightValue) =
	if _rightValue == 0
		then throwDyn ZeroDivisionError
		else return $ PythonInteger $ _leftValue `div` _rightValue

division (PythonFloatingPoint _leftValue) (PythonFloatingPoint _rightValue) =
	if _rightValue == 0
		then throwDyn ZeroDivisionError
		else return $ PythonFloatingPoint $ _leftValue / _rightValue

division (PythonComplex _leftValue) (PythonComplex _rightValue) =
	if _rightValue == 0 :+ 0
		then throwDyn ZeroDivisionError
		else return $ PythonComplex $ _leftValue / _rightValue

division _ _ =
	throwDyn TypeError

floorDivision :: PythonObject -> PythonObject -> IO PythonObject

floorDivision _leftObject@(PythonBoolean _) _rightObject@(PythonBoolean _) =
	division _leftObject _rightObject

floorDivision _leftObject@(PythonInteger _) _rightObject@(PythonInteger _) =
	division _leftObject _rightObject

floorDivision (PythonFloatingPoint _leftValue) (PythonFloatingPoint _rightValue) =
	if _rightValue == 0
		then throwDyn ZeroDivisionError
		else 
			return $ 
				PythonFloatingPoint $ 
					fromInteger $ floor $ _leftValue / _rightValue

floorDivision _ _ =
	throwDyn TypeError

modulo :: PythonObject -> PythonObject -> IO PythonObject

modulo (PythonBoolean _leftValue) (PythonBoolean _rightValue) =
	modulo 
		(PythonInteger $ boolToInteger _leftValue) 
		(PythonInteger $ boolToInteger _rightValue)

modulo (PythonInteger _leftValue) (PythonInteger _rightValue) =
	if _rightValue == 0
		then throwDyn ZeroDivisionError
		else return $ PythonInteger $ _leftValue `mod` _rightValue

modulo (PythonFloatingPoint _leftValue) (PythonFloatingPoint _rightValue) =
	if _rightValue == 0
		then throwDyn ZeroDivisionError
		else 
			return $ 
				PythonFloatingPoint $ 
					_leftValue - (fromInteger $ floor $ _leftValue / _rightValue) * _rightValue
modulo _ _ =
	throwDyn TypeError

(^^^) :: Double -> Double -> Double

infixr 8 ^^^

_base ^^^ _exponent =
	exp $ log _base * _exponent

power :: PythonObject -> PythonObject -> IO PythonObject

power (PythonBoolean _leftValue) (PythonBoolean _rightValue) =
	power
		(PythonInteger $ boolToInteger _leftValue) 
		(PythonInteger $ boolToInteger _rightValue)

power (PythonInteger _leftValue) (PythonInteger _rightValue) =
	if _rightValue < 0
		then
			power
				(PythonFloatingPoint $ fromInteger _leftValue) 
				(PythonFloatingPoint $ fromInteger _rightValue)
		else 
			return $ PythonInteger $ _leftValue ^ _rightValue

power (PythonFloatingPoint _leftValue) (PythonFloatingPoint _rightValue) =
	return $ PythonFloatingPoint $ _leftValue ^^^ _rightValue

power (PythonComplex _leftValue) (PythonComplex _rightValue) =
	let
		r = magnitude _leftValue
		p = phase _leftValue
		c = realPart _rightValue
		d = imagPart _rightValue
		x = d * log r + c * p
	in
		return $ 
			PythonComplex $ 
				(r ^^^ c * exp (-d * p) :+ 0) * (cos x :+ sin x)
	
power _ _ =
	throwDyn TypeError

sequenceEqualTo' :: [IO Bool] -> IO Bool

sequenceEqualTo' [] =
	return True

sequenceEqualTo' (_elementEquality:_restElementEqualities) = do
	_elementEquality' <- _elementEquality
	if _elementEquality'
		then sequenceEqualTo' _restElementEqualities
		else return False

sequenceEqualTo :: [PythonObject] -> [PythonObject] -> IO Bool
sequenceEqualTo _leftElements _rightElements =
	if length _leftElements == length _rightElements
		then do
			sequenceEqualTo' $
				zipWith
					(\_leftElement _rightElement -> 
						applyToCoerced equalTo _leftElement _rightElement
					)
					_leftElements
					_rightElements
		else return False

equalTo :: PythonObject -> PythonObject -> IO Bool

equalTo (PythonBoolean _leftValue) (PythonBoolean _rightValue) =
	return $ _leftValue == _rightValue

equalTo (PythonInteger _leftValue) (PythonInteger _rightValue) =
	return $ _leftValue == _rightValue

equalTo (PythonFloatingPoint _leftValue) (PythonFloatingPoint _rightValue) =
	return $ _leftValue == _rightValue

equalTo (PythonComplex _leftValue) (PythonComplex _rightValue) =
	return $ _leftValue == _rightValue

equalTo (PythonString _leftValue) (PythonString _rightValue) =
	return $ _leftValue == _rightValue

equalTo (PythonList _leftElements) (PythonList _rightElements) =
	_leftElements `sequenceEqualTo` _rightElements

equalTo (PythonTuple _leftElements) (PythonTuple _rightElements) =
	_leftElements `sequenceEqualTo` _rightElements
	
equalTo _ _ =
	return False
	
notEqualTo :: PythonObject -> PythonObject -> IO Bool
notEqualTo _leftObject _rightObject = do
	_areEqual <- _leftObject `equalTo` _rightObject
	return $ not _areEqual
	
-- TODO:
evaluateComparisonExpression 
	:: PythonObject 
	-> [(ComparisonOperator, Expression)] 
	-> StateT ExecutionFrame IO PythonObject

evaluateComparisonExpression _leftObject [] =
	return $ PythonBoolean True

evaluateComparisonExpression _leftObject ((_operator, _rightExpression):_restComparisons) =
	let
		_operator' =
			case _operator of
-- TODO:
--				LessOperator -> lessThan
--				GreaterOperator -> greaterThan
				EqualOperator -> equalTo
				NotEqualOperator -> notEqualTo
--				LessOrEqualOperator -> lessOrEqualThan
--				GreaterOrEqualOperator -> greaterOrEqualThan
--				IsOperator -> is
--				IsNotOperator -> isNot
--				InOperator -> in'
--				NotInOperator -> notIn
				_ -> undefined
	in do
		_rightObject <- evaluateExpression _rightExpression
		_isTrue <- lift $ applyToCoerced _operator' _leftObject _rightObject
		if _isTrue
			then evaluateComparisonExpression _rightObject _restComparisons
			else return $ PythonBoolean False

-- TODO:
evaluateExpression :: Expression -> StateT ExecutionFrame IO PythonObject

evaluateExpression (IdentifierExpression _name) = do
	_executionFrame <- get
	case Map.lookup _name (nameToObjectMap _executionFrame) of
		Just _value -> return _value
		Nothing -> throwDyn NameError

evaluateExpression (BooleanLiteralExpression _value) =
	return $ PythonBoolean _value

evaluateExpression (StringLiteralExpression _value) =
	return $ PythonString _value

evaluateExpression (IntegerLiteralExpression _value) =
	return $ PythonInteger _value

evaluateExpression (UnaryExpression _operator _subexpression) =
	let
		_operator' = 
			case _operator of
				LogicalNotOperator -> logicalNot
				BitwiseNotOperator -> bitwiseNot
				UnaryPlusOperator -> unaryPlus
				UnaryMinusOperator -> unaryMinus
	in do
		_evaluatedSubexpression <- evaluateExpression _subexpression
		lift $ _operator' _evaluatedSubexpression
		
evaluateExpression 
	(BinaryExpression 
		LogicalAndOperator 
		_leftSubexpression 
		_rightSubexpression
	)
	= do
		_evaluatedLeftSubexpression <- evaluateExpression _leftSubexpression
		if isTrue _evaluatedLeftSubexpression
			then evaluateExpression _rightSubexpression
			else return _evaluatedLeftSubexpression

evaluateExpression 
	(BinaryExpression 
		LogicalOrOperator 
		_leftSubexpression 
		_rightSubexpression
	)
	= do
		_evaluatedLeftSubexpression <- evaluateExpression _leftSubexpression
		if isTrue _evaluatedLeftSubexpression
			then return _evaluatedLeftSubexpression
			else evaluateExpression _rightSubexpression

evaluateExpression (BinaryExpression _operator _leftSubexpression _rightSubexpression) =
	let
		_operator' =
			case _operator of
				LogicalAndOperator -> error "Internal error: LogicalAndOperator should be matched by evaluateExpression"
				LogicalOrOperator -> error "Internal error: LogicalOrOperator should be matched by evaluateExpression"
				BitwiseAndOperator -> bitwiseAnd
				BitwiseOrOperator -> bitwiseOr
				BitwiseXorOperator -> bitwiseXor
				LeftShiftOperator -> leftShift
				RightShiftOperator -> rightShift
				AdditionOperator -> addition
				SubtractionOperator -> subtraction
				MultiplicationOperator -> multiplication
				DivisionOperator -> division
				FloorDivisionOperator -> floorDivision
				ModuloOperator -> modulo
				PowerOperator -> power
	in do
		_leftObject <- evaluateExpression _leftSubexpression
		_rightObject <- evaluateExpression _rightSubexpression
		lift $ applyToCoerced _operator' _leftObject _rightObject
		
evaluateExpression (ComparisonExpression _firstExpression _comparisons) = do
	_firstObject <- evaluateExpression _firstExpression
	evaluateComparisonExpression _firstObject _comparisons

evaluateExpression (ListExpression _elementExpressions) = do
	_elements <- mapM evaluateExpression _elementExpressions
	return $ PythonList _elements
	
evaluateExpression _ =
	undefined
	
evaluateExpressionsAsTuple :: [Expression] -> StateT ExecutionFrame IO PythonObject
evaluateExpressionsAsTuple _expressions = do
	_objects <- mapM evaluateExpression _expressions
	return $
		if length _objects == 1
			then head _objects
			else PythonTuple _objects

interpretIfStatement :: [(Expression, [Statement])] -> [Statement] -> StateT ExecutionFrame IO ()

interpretIfStatement [] _elseSuite =
	interpretStatements _elseSuite

interpretIfStatement ((_ifCondition, _thenSuite):_restIfClauses) _elseSuite = do
	_evaluatedIfCondition <- evaluateExpression _ifCondition
	if isTrue _evaluatedIfCondition
		then interpretStatements _thenSuite
		else interpretIfStatement _restIfClauses _elseSuite

-- TODO:
assign' :: Expression -> PythonObject -> StateT ExecutionFrame IO ()

assign' (IdentifierExpression _name) _object =
	modify $ 
		\_executionFrame -> 
			_executionFrame
			{
				nameToObjectMap =
					Map.insert 
						_name 
						_object 
						(nameToObjectMap _executionFrame)
			}

assign' (SubscriptionExpression _sequenceExpression _subscriptExpressions) _object = do
	_sequenceObject <- evaluateExpression _sequenceExpression
	_newSequenceObject <-
		let
			newElements _elements = do
				_subscriptObject <- evaluateExpressionsAsTuple _subscriptExpressions
				case _subscriptObject of
					PythonInteger _subscriptValue -> 
						let
							_subscript = fromInteger _subscriptValue
							(_beforeSubscript, _afterSubscript) = splitAt _subscript _elements
						in do
							when (_subscript >= length _elements) (throwDyn IndexError)
							return $ _beforeSubscript ++ (_object:tail _afterSubscript)
					_ -> throwDyn TypeError
		in
			case _sequenceObject of
				PythonList _elements -> do
					_newElements <- newElements _elements
					return $ PythonList _newElements
				PythonTuple _elements -> do
					_newElements <- newElements _elements
					return $ PythonTuple _newElements
				_ -> throwDyn TypeError
	assign' _sequenceExpression _newSequenceObject
	
assign' _ _ =
	undefined

-- TODO:
assign :: Target -> PythonObject -> StateT ExecutionFrame IO ()

assign (SingleTarget _expression) _object =
	assign' _expression _object

assign _ _ =
	throwDyn TypeError
	
interpretStatement :: Statement -> StateT ExecutionFrame IO ()

interpretStatement (AssignmentStatement _targets _expressions) = do
	_object <- evaluateExpressionsAsTuple _expressions
	foldM
		(\_ _target -> assign _target _object)
		()
		_targets

interpretStatement (ExpressionStatement _expressions) = do
	foldM
		(\_ _expression -> do
			evaluateExpression _expression
			return ()
		)
		()
		_expressions

interpretStatement (IfStatement _ifClauses _elseSuite) =
	interpretIfStatement _ifClauses _elseSuite

-- TODO: file should be considered
-- TODO: soft spaces
interpretStatement (PrintStatement _maybeFile _expressions _endOfLine) = do
	foldM
		(\_ _expression -> do
			_object <- evaluateExpression _expression
			lift $ putStr $ 
				objectToString _object (\_value -> "'" ++ _value ++ "'") ++ " "
		)
		()
		_expressions
	if _endOfLine
		then lift $ putStrLn ""
		else return ()

interpretStatements :: [Statement] -> StateT ExecutionFrame IO ()
interpretStatements _statements =
	foldM (const interpretStatement) () _statements

interpret :: [Statement] -> IO ()
interpret _statements =
	evalStateT 
		(interpretStatements _statements) 
		(ExecutionFrame Nothing Map.empty)
